from chimera.util.coord import Coord
from chimera.util.enum import Enum
from chimera.util.image import Image
from elixir import Entity, Field
from elixir import DateTime, Numeric, String, Text
from elixir import OneToMany, ManyToOne, ManyToMany
from elixir import setup_all, create_all, metadata, session
import math

StarFields = Enum('RA', 'DEC', 'NAME', 'CATALOGS', 'MAGNITUDES', 'MAGFILTERS', 'EXPSTARCOUNT')
ExposureFields = Enum('RA', 'DEC', 'ALT', 'AZ', 'OBSDATE', 'LST', 'LATITUDE', 'LONGITUDE', 'FILTER', 'OBSERVER', 'IMAGETYPE', 'EXPTIME', 'FILENAME', 'EXPSTARCOUNT', 'NOTES')
ExpStarFields = Enum('STAR', 'EXPOSURE', 'ALT', 'AZ', 'FWHM', 'FLUX', 'MAGNITUDE', 'AIRMASS')
ExtinctionFields = Enum('STARTTIME', 'ENDTIME', 'FILTER', 'COEFFICIENT')
Tables = Enum('STAR', 'EXPOSURE', 'EXPSTAR', 'EXTINCTION')
FilterOps = Enum('EXACT', 'MIN', 'MAX')

def connect(filename = "photometry.db"):
    metadata.bind = "sqlite:///" + filename
    setup_all()

if __name__ == "__main__":
    print "This module is not intended to be run as a script."

class Star(Entity):
    objname = Field(Text)
    ra = Field(Numeric(18,15))  # Decimal degrees
    dec = Field(Numeric(18,15)) # Decimal degrees
    catalogs = ManyToMany('Catalog')
    magnitudes = OneToMany('StarMagnitude')
    exposures = OneToMany('ExposureStar')

    @classmethod
    def filterQuery(cls, q, field = StarFields.RA, op = FilterOps.EXACT, value = 0):
        if field == StarFields.RA:
            if op == FilterOps.EXACT:
                return q.filter(Star.ra == value)
            elif op == FilterOps.MAX:
                return q.filter(Star.ra <= value)
            elif op == FilterOps.MIN:
                return q.filter(Star.ra >= value)
        elif field == StarFields.DEC:
            if op == FilterOps.EXACT:
                return q.filter(Star.dec == value)
            elif op == FilterOps.MAX:
                return q.filter(Star.dec <= value)
            elif op == FilterOps.MIN:
                return q.filter(Star.dec >= value)
        elif field == StarFields.NAME:
            if op == FilterOps.EXACT:
                return q.filter(Star.objname == value)
        elif field == StarFields.CATALOGS:
            if op == FilterOps.EXACT:
                return q.filter(Star.catalogs.any(name = value))
        elif field == StarFields.MAGNITUDES:
            if op == FilterOps.EXACT:
                return q.filter(Star.magnitudes.any(StarMagnitude.magnitude == value))
            elif op == FilterOps.MAX:
                return q.filter(~Star.magnitudes.any(StarMagnitude.magnitude > value))
            elif op == FilterOps.MIN:
                return q.filter(~Star.magnitudes.any(StarMagnitude.magnitude < value))
        elif field == StarFields.MAGFILTERS:
            if op == FilterOps.EXACT:
                return q.filter(Star.magnitudes.any(filter = value))
        elif field == StarFields.EXPSTARCOUNT:
            if (op == FilterOps.MIN) and (value == 1):
                return q.join(Star.exposures)
        return q

    def getField(self, field):
        if field == StarFields.RA:
            return Coord.fromD(float(self.ra)).toHMS()
        elif field == StarFields.DEC:
            return Coord.fromD(float(self.dec)).toDMS()
        elif field == StarFields.NAME:
            return str(self.objname)
        elif field == StarFields.CATALOGS:
            return [str(c.name) for c in self.catalog]
        elif field == StarFields.MAGNITUDES:
            magnitudes = {}
            for magnitude in self.magnitudes:
                magnitudes[str(magnitude.filter)] = float(magnitude.magnitude)
            return magnitudes
        elif field == StarField.MAGFILTERS:
            return [str(m.filter) for m in self.magnitudes]
        elif field == StarFields.EXPSTARCOUNT:
            return len(self.exposures)
        return None

    #In this function, ra and dec are in decimal degrees, as is the distance
    def getDistanceFromRADec(self, ra, dec):
        diffRA = (float(self.ra) - ra)
        diffRA = diffRA * diffRA
        diffDec = (float(self.dec) - dec)
        diffDec = diffDec * diffDec
        return math.sqrt(diffRA + diffDec)

    def __repr__(self):
        return '<datamodel.Star "%s" (%s %s)>' % (self.objname, self.ra, self.dec)

class Catalog(Entity):
    name = Field(Text)
    stars = ManyToMany('Star')
    
class StarMagnitude(Entity):
    star = ManyToOne('Star', ondelete='cascade')
    source = ManyToOne('Catalog')
    filter = Field(String(5)) # U, B, V, R, I, etc.
    magnitude = Field(Numeric(10, 5))
    
class Exposure(Entity):
    filename = Field(Text)
    ra = Field(Numeric(18,15))  # Decimal degrees
    dec = Field(Numeric(18,15)) # Decimal degrees
    alt = Field(Numeric(18,15)) # Decimal degrees
    az = Field(Numeric(18,15))  # Decimal degrees
    lst = Field(Numeric(18,15)) # Decimal degrees
    latitude = Field(Numeric(18,15)) # Decimal degrees
    longitude = Field(Numeric(18,15)) # Decimal degrees
    obsdate = Field(DateTime) # Date & time
    imagetype = Field(String(10)) # object, dark, flat, etc.
    exptime = Field(Numeric) # Decimal seconds
    filter = Field(String(5)) # U, B, V, R, I, etc.
    observer = Field(Text)
    stars = OneToMany('ExposureStar')
    tasks = OneToMany('ExposureTask')

    @classmethod
    def filterQuery(cls, q, field = ExposureFields.RA, op = FilterOps.EXACT, value = 0):
        if field == ExposureFields.OBSDATE:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.obsdate == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.obsdate <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.obsdate >= value)
        elif field == ExposureFields.LST:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.lst == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.lst <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.lst >= value)
        elif field == ExposureFields.LATITUDE:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.latitude == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.latitude <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.latitude >= value)
        elif field == ExposureFields.LONGITUDE:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.longitude == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.longitude <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.longitude >= value)
        elif field == ExposureFields.FILTER:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.filter == value)
        elif field == ExposureFields.OBSERVER:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.observer == value)
        elif field == ExposureFields.EXPTIME:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.exptime == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.exptime <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.exptime >= value)
        elif field == ExposureFields.IMAGETYPE:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.imagetype == value)
        elif field == ExposureFields.RA:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.ra == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.ra <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.ra >= value)
        elif field == ExposureFields.DEC:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.dec == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.dec <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.dec >= value)
        elif field == ExposureFields.ALT:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.alt == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.alt <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.alt >= value)
        elif field == ExposureFields.AZ:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.az == value)
            elif op == FilterOps.MAX:
                return q.filter(Exposure.az <= value)
            elif op == FilterOps.MIN:
                return q.filter(Exposure.az >= value)
        elif field == ExposureFields.FILENAME:
            if op == FilterOps.EXACT:
                return q.filter(Exposure.filename == value)
        elif field == ExposureFields.EXPSTARCOUNT:
            if (op == FilterOps.MIN) and (value == 1):
                return q.join(Exposure.stars)
        elif field == ExposureFields.NOTES:
            if op == FilterOps.EXACT:
                return q.join(Exposure.tasks).filter(ExposureTask.name == value)
        return q

    def getField(self, field):
        if field == ExposureFields.OBSDATE:
            return self.obsdate
        elif field == ExposureFields.LST:
            return Coord.fromD(float(self.lst)).toHMS()
        elif field == ExposureFields.LATITUDE:
            return Coord.fromD(float(self.latitude)).toDMS()
        elif field == ExposureFields.LONGITUDE:
            return Coord.fromD(float(self.longitude)).toDMS()
        elif field == ExposureFields.FILTER:
            return str(self.filter)
        elif field == ExposureFields.OBSERVER:
            return str(self.observer)
        elif field == ExposureFields.EXPTIME:
            return float(self.exptime)
        elif field == ExposureFields.IMAGETYPE:
            return str(self.imagetype)
        elif field == ExposureFields.RA:
            return Coord.fromD(float(self.ra)).toHMS()
        elif field == ExposureFields.DEC:
            return Coord.fromD(float(self.dec)).toDMS()
        elif field == ExposureFields.ALT:
            return Coord.fromD(float(self.alt)).toDMS()
        elif field == ExposureFields.AZ:
            return Coord.fromD(float(self.az)).toDMS()
        elif field == ExposureFields.FILENAME:
            return str(self.filename)
        elif field == ExposureFields.EXPSTARCOUNT:
            return len(self.stars)
        elif field == ExposureFields.NOTES:
            notes = {}
            for note in self.tasks:
                notes[str(note.name)] = note.date
            return notes
        return None
    
    def toImage(self):
        return Image.fromFile(str(self.filename), fix=False)

    def __repr__(self):
        return '<datamodel.Exposure "%s">' % (self.filename)

class ExposureStar(Entity):
    exposure = ManyToOne('Exposure', required=True, ondelete='cascade')
    star = ManyToOne('Star', required=True)
    #NOTE: photra and photdec are for troubleshooting star identification errors only
    photra = Field(Numeric(18,15))  # RA retrieved by photometry (decimal degrees)
    photdec = Field(Numeric(18,15)) # Dec retrieved by photometry (decimal degrees)
    alt = Field(Numeric(18,15))     # Altitude (decimal degrees)
    az = Field(Numeric(18,15))      # Azimuth (decimal degrees)
    fwhm = Field(Numeric(12,5))     # FWHM
    flux = Field(Numeric(12,5))     # Flux
    magnitude = Field(Numeric(10,5))# Magnitude
    airmass = Field(Numeric(18,15)) # Airmass

    @classmethod
    def filterQuery(cls, q, field = ExpStarFields.FWHM, op = FilterOps.EXACT, value = 0):
        if field == ExpStarFields.ALT:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.alt == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.alt <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.alt >= value)
        elif field == ExpStarFields.AZ:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.az == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.az <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.az >= value)
        elif field == ExpStarFields.FWHM:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.fwhm == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.fwhm <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.fwhm >= value)
        elif field == ExpStarFields.FLUX:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.flux == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.flux <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.flux >= value)
        elif field == ExpStarFields.MAGNITUDE:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.magnitude == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.magnitude <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.magnitude >= value)
        elif field == ExpStarFields.AIRMASS:
            if op == FilterOps.EXACT:
                return q.filter(ExposureStar.airmass == value)
            elif op == FilterOps.MAX:
                return q.filter(ExposureStar.airmass <= value)
            elif op == FilterOps.MIN:
                return q.filter(ExposureStar.airmass >= value)
        return q

    def getField(self, field):
        if field == ExpStarFields.ALT:
            return Coord.fromD(float(self.alt)).toDMS()
        elif field == ExpStarFields.AZ:
            return Coord.fromD(float(self.az)).toDMS()
        elif field == ExpStarFields.FWHM:
            return float(self.fwhm)
        elif field == ExpStarFields.FLUX:
            return float(self.flux)
        elif field == ExpStarFields.MAGNITUDE:
            return float(self.magnitude)
        elif field == ExpStarFields.AIRMASS:
            return float(self.airmass)
        elif field == ExpStarFields.STAR:
            return self.star
        elif field == ExpStarFields.EXPOSURE:
            return self.exposure
        return None

class ExposureTask(Entity):
    exposure = ManyToOne('Exposure', required=True, ondelete='cascade')
    name = Field(Text)
    detail = Field(Text)
    date = Field(DateTime)

    def __repr__(self):
        return '<datamodel.ExposureNote "%s" (%s)>' % (self.name, self.date)
    
class Extinction(Entity):
    starttime = Field(DateTime)
    endtime = Field(DateTime)
    refband = Field(String(5))
    otherbands = Field(Text)
    coefficient1 = Field(Numeric(18,15))
    coefficient2 = Field(Numeric(18,15))
    dataused = ManyToMany('ExposureStar')